/********************************************
 * 功能说明: 
 * 模块名称: 
 * 系统名称: 
 * 软件版权: 北京银杉金服科技有限公司
 * 系统版本: 1.0.0
 * 开发人员: zhangfb
 * 开发时间: 2018/9/30 22:22
 * 审核人员: 
 * 相关文档: 
 * 修改记录: 修改日期 修改人员 修改说明
 *********************************************/
package com.hyacinth.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.hyacinth.constant.ErrorCodeEnum;
import com.hyacinth.dto.LoginUser;
import com.hyacinth.exception.BusinessException;
import com.hyacinth.util.JwtTokenUtils;
import com.hyacinth.util.XaUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;

/**
 * 自定义授权过滤器
 * 验证用户名密码正确后，生成一个token，并将token返回给客户端
 * 该类继承自UsernamePasswordAuthenticationFilter，重写了其中的2个方法
 * attemptAuthentication ：接收并解析用户凭证。
 * successfulAuthentication ：用户成功登录后，这个方法会被调用，我们在这个方法里生成token。
 * @author zhangfb
 * @version 1.0.0.1
 * @since JDK 1.8
 */
@Slf4j
public class CustomAuthorizationFilter extends UsernamePasswordAuthenticationFilter {

    private ThreadLocal<Integer> rememberMe = new ThreadLocal<>();
    private AuthenticationManager authenticationManager;

    public CustomAuthorizationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    /**
     * 接收并解析用户凭证
     * @param request   请求
     * @param response  响应
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        // 从输入流中获取到登录的信息
        try {
            LoginUser user = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
            log.info("登陆用户信息,user={}",user);
            String userName = user.getUsername();
            String password = user.getPassword();
            this.rememberMe.set(user.getRememberMe());
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password, Lists.newArrayList());
            return authenticationManager.authenticate(token);
        } catch (Exception e) {
            log.error("接收并解析用户凭证出错", e);
            throw new BusinessException(ErrorCodeEnum.UAC10010005);
        }
    }

    /**
     * 用户成功登录后，这个方法会被调用，我们在这个方法里生成token。
     * @param request    请求
     * @param response   响应
     * @param chain      过滤连
     * @param authResult 验证结果
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        // 获取授权用户
        SecurityUser user = (SecurityUser) authResult.getPrincipal();
        log.info("授权用户信息,user={}",user);
        if (user != null) {
            // 获取权限
            String role = StringUtils.EMPTY;
            if (!XaUtils.isEmpty(user.getRoles())) {
                role = StringUtils.join(user.getRoles().toArray(), ",");
            }
            boolean isRemember = (1 == rememberMe.get());
            // 根据用户名，角色创建token
            String token = JwtTokenUtils.createToken(user.getName(), role, isRemember);
                response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
        }

    }

    // 验证失败时候调用的方法
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.getWriter().write("鉴权失败, reason: " + failed.getMessage());
    }

}
